#!/usr/bin/python3
# -*- coding: utf-8 -*-

from os.path import isfile, join as joinpath, relpath, basename
from glob import iglob
from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED

from .validators import reject_if
from .callbacks import CallbackLogger
from .command import open_for_write, Environment, Command


class ZipCommand(Command):
    """ 用于执行创建或者更新Zip压缩包的命令。

    路径规则：
    每个源文件集合包含3个部分: root, src, dst
    如果未指定`root`且未指定`dst`，那么将`src`写入压缩包的根目录，文件名不变。
    如果未指定`root`而指定`dst`，那么将`src`写入压缩包，使用`dst`作为压缩包内的路径，此时`src`必须表示单一文件，`dst`可以包含目录。
    如果指定了`root`，那么将`src`写入压缩包，使用相对于`root`的路径作为压缩包内的路径，忽略`dst`。
    """
    __slots__ = ('__srcs', '__dst')

    def __init__(self,
                 srcs: tuple,
                 dst: str,
                 description: str = '',
                 check_required: bool = False
                 ):
        """ 构造Zip命令。

        :param srcs: 源文件集合。
        :param dst: 压缩包文件。
        """
        super().__init__(description, check_required)

        reject_if(lambda: not srcs)
        reject_if(lambda: not dst)

        self.__srcs = srcs
        self.__dst = dst

    @property
    def srcs(self):
        return self.__srcs

    @property
    def dst(self):
        return self.__dst

    def interpolate_srcs(
            self,
            srcs: tuple,
            environment: Environment
    ):
        for src_ in srcs:
            root_, src_, dst_, *etc = [*src_, None, None]
            root_ = self.interpolate(root_, environment)
            src_ = self.interpolate(src_, environment)
            dst_ = self.interpolate(dst_, environment)
            if not root_:
                src_ = self.abspath(src_, environment)
            else:
                root_ = self.abspath(root_, environment)
            yield root_, src_, dst_

    def __call__(
            self,
            environment: Environment,
            resources: list,
            callback: CallbackLogger
    ):
        reject_if(lambda: environment is None)
        reject_if(lambda: resources is None)

        srcs = tuple(self.interpolate_srcs(self.srcs, environment))
        dst = self.abspath(self.interpolate(self.dst, environment), environment)

        callback.info('create zip file {dst}', dst=dst)
        with open_for_write(dst) as uf:
            with ZipFile(uf, mode='w', compression=ZIP_DEFLATED, compresslevel=5) as zf:
                for root_, src_, dst_ in srcs:
                    if not root_ and not dst_:
                        for f in iglob(src_):
                            dst_ = basename(f)
                            callback.verbose('zip {src} to {dst}', src=f, dst=dst_)
                            zf.write(f, dst_)
                    elif not root_:
                        if not isfile(src_):
                            callback.warn('cannot find file {src}', src=src_)
                            continue
                        callback.verbose('zip {src} to {dst}', src=src_, dst=dst_)
                        zf.write(src_, dst_)
                    else:
                        p = joinpath(root_, src_)
                        for f in iglob(p, recursive=True):
                            dst_ = relpath(f, root_)
                            if dst_ == '.':
                                continue
                            if dst_.startswith('.'):
                                callback.warn('Illegal src {src}', src=f)
                                continue

                            callback.verbose('zip {src} to {dst}', src=f, dst=dst_)
                            zf.write(f, dst_)

        if self.check_required:
            if not isfile(dst) or not is_zipfile(dst):
                raise RuntimeError('cannot write zip file ' + dst)
